{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "d1f20875-c50d-46df-906b-49ec1d0c6002",
   "metadata": {},
   "source": [
    "## 10.12 ResNet的实现\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "c90a18b9-d8e8-431c-856c-35f1e4ed05e1",
   "metadata": {},
   "source": [
    "### 1.任务描述\n",
    "\n",
    "使用TensorFlow实现ResNet，对CIFAR-10数据集进行训练，实现多分类。"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "f5b4fc39-cbcf-432a-bf1e-e75e642d4b87",
   "metadata": {},
   "source": [
    "### 2.知识准备\n",
    "\n",
    "见教程。"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "55043130-4496-43a3-803b-9bc1cea8b1b8",
   "metadata": {},
   "source": [
    "### 3.任务分析构\n",
    "\n",
    "为了能更方便地搭建ResNet网络，先构建ResNetBlock类。\n",
    "\n",
    "ResNetBlock类中除了CBA，还有用来表示是否对输入进行下采样的变量residual_path。\n",
    "\n",
    "ResNet类先由一个CBA层处理输入后，将输入传递给按深度排列的ResNetBlock块进行训练。ResNetBlock块训练完之后经过全局均值池化层进行池化操作，最后由一个全连接层输出结果。"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "435c6090-cfda-4f46-a550-22a368e41e4a",
   "metadata": {},
   "source": [
    "### 4.任务实施\n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "id": "ec75eb6c-5da3-467d-a471-ca3b47242dd6",
   "metadata": {},
   "source": [
    "执行代码"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "2ae9da58-e339-4d22-9f8d-ca255711d89e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Epoch 1/5\n",
      "1563/1563 [==============================] - 63s 37ms/step - loss: 1.3491 - sparse_categorical_accuracy: 0.5583 - val_loss: 2.0056 - val_sparse_categorical_accuracy: 0.4496\n",
      "Epoch 2/5\n",
      "1563/1563 [==============================] - 68s 44ms/step - loss: 0.8069 - sparse_categorical_accuracy: 0.7349 - val_loss: 0.9047 - val_sparse_categorical_accuracy: 0.7032\n",
      "Epoch 3/5\n",
      "1563/1563 [==============================] - 62s 40ms/step - loss: 0.6209 - sparse_categorical_accuracy: 0.7960 - val_loss: 0.7807 - val_sparse_categorical_accuracy: 0.7405\n",
      "Epoch 4/5\n",
      "1563/1563 [==============================] - 58s 37ms/step - loss: 0.5037 - sparse_categorical_accuracy: 0.8366 - val_loss: 0.6457 - val_sparse_categorical_accuracy: 0.7929\n",
      "Epoch 5/5\n",
      "1563/1563 [==============================] - 62s 39ms/step - loss: 0.3947 - sparse_categorical_accuracy: 0.8756 - val_loss: 0.6413 - val_sparse_categorical_accuracy: 0.7990\n",
      "Model: \"res_net18\"\n",
      "_________________________________________________________________\n",
      " Layer (type)                Output Shape              Param #   \n",
      "=================================================================\n",
      " conv2d (Conv2D)             multiple                  1728      \n",
      "                                                                 \n",
      " batch_normalization (BatchN  multiple                 256       \n",
      " ormalization)                                                   \n",
      "                                                                 \n",
      " activation (Activation)     multiple                  0         \n",
      "                                                                 \n",
      " sequential (Sequential)     (None, 4, 4, 512)         11176448  \n",
      "                                                                 \n",
      " global_average_pooling2d (G  multiple                 0         \n",
      " lobalAveragePooling2D)                                          \n",
      "                                                                 \n",
      " dense (Dense)               multiple                  5130      \n",
      "                                                                 \n",
      "=================================================================\n",
      "Total params: 11,183,562\n",
      "Trainable params: 11,173,962\n",
      "Non-trainable params: 9,600\n",
      "_________________________________________________________________\n"
     ]
    }
   ],
   "source": [
    "# 1，导入相关模块\n",
    "import tensorflow as tf\n",
    "from tensorflow.keras.layers import Conv2D, BatchNormalization, Activation, MaxPool2D, Dropout, Flatten, Dense\n",
    "from tensorflow.keras import Model\n",
    "# 2，加载数据集\n",
    "(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()\n",
    "x_train, x_test = x_train / 255.0, x_test / 255.0\n",
    "# 3，搭建网络\n",
    "class ResnetBlock(Model):\n",
    "    def __init__(self, filters, strides=1, residual_path=False):\n",
    "        super(ResnetBlock, self).__init__()\n",
    "        self.filters = filters\n",
    "        self.strides = strides\n",
    "        self.residual_path = residual_path\n",
    "\n",
    "        self.c1 = Conv2D(filters, (3, 3), strides=strides, padding='same', use_bias=False)\n",
    "        self.b1 = BatchNormalization()\n",
    "        self.a1 = Activation('relu')\n",
    "\n",
    "        self.c2 = Conv2D(filters, (3, 3), strides=1, padding='same', use_bias=False)\n",
    "        self.b2 = BatchNormalization()\n",
    "\n",
    "        # 当residual_path为True时，对输入进行下采样，即用1×1的卷积核进行卷积操作，保证x能和F(x)的维度相同，顺利相加\n",
    "        if residual_path:\n",
    "            self.down_c1 = Conv2D(filters, (1, 1), strides=strides, padding='same', use_bias=False)\n",
    "            self.down_b1 = BatchNormalization()\n",
    "        \n",
    "        self.a2 = Activation('relu')\n",
    "\n",
    "    def call(self, inputs):\n",
    "        # residual等于输入值本身，即residual=x\n",
    "        residual = inputs  \n",
    "        # 将输入通过卷积层、BN层、激活层，计算F(x)\n",
    "        x = self.c1(inputs)\n",
    "        x = self.b1(x)\n",
    "        x = self.a1(x)\n",
    "\n",
    "        x = self.c2(x)\n",
    "        y = self.b2(x)\n",
    "\n",
    "        if self.residual_path:\n",
    "            residual = self.down_c1(inputs)\n",
    "            residual = self.down_b1(residual)\n",
    "        # 最后输出的是两部分的和，即F(x)+x或F(x)+W(x)，再经过激活函数\n",
    "        out = self.a2(y + residual)  \n",
    "        return out\n",
    "\n",
    "# 自定义网络模型类\n",
    "class ResNet18(Model):\n",
    "    # block_list：每个block有几个卷积层\n",
    "    def __init__(self, block_list, initial_filters=64):\n",
    "        super(ResNet18, self).__init__()\n",
    "        # 共有几个block\n",
    "        self.num_blocks = len(block_list)  \n",
    "        self.block_list = block_list\n",
    "        self.out_filters = initial_filters\n",
    "        self.c1 = Conv2D(self.out_filters, (3, 3), strides=1, padding='same', use_bias=False)\n",
    "        self.b1 = BatchNormalization()\n",
    "        self.a1 = Activation('relu')\n",
    "        self.blocks = tf.keras.models.Sequential()\n",
    "        # 构建ResNet结构\n",
    "        # 第几个resnet block\n",
    "        for block_id in range(len(block_list)):  \n",
    "            # 第几个卷积层\n",
    "            for layer_id in range(block_list[block_id]):  \n",
    "                # 对除第一个block外的每个block的输入进行下采样\n",
    "                if block_id != 0 and layer_id == 0:  \n",
    "                    block = ResnetBlock(self.out_filters, strides=2, residual_path=True)\n",
    "                else:\n",
    "                    block = ResnetBlock(self.out_filters, residual_path=False)\n",
    "                # 将构建好的block加入ResNet\n",
    "                self.blocks.add(block) \n",
    "            # 下一个block的卷积核数是上一个block的卷积核数的2倍 \n",
    "            self.out_filters *= 2  \n",
    "    \n",
    "        self.p1 = tf.keras.layers.GlobalAveragePooling2D()\n",
    "        # 全连接输出\n",
    "        self.f1 = Dense(10, activation='softmax', kernel_regularizer=tf.keras.regularizers.l2())\n",
    "\n",
    "    def call(self, inputs):\n",
    "        x = self.c1(inputs)\n",
    "        x = self.b1(x)\n",
    "        x = self.a1(x)\n",
    "        x = self.blocks(x)\n",
    "        x = self.p1(x)\n",
    "        y = self.f1(x)\n",
    "        return y    \n",
    "    \n",
    "# 实例化模型对象\n",
    "model = ResNet18([2, 2, 2, 2])\n",
    "\n",
    "# 4，配置网络\n",
    "model.compile(\n",
    "    # 优化器\n",
    "    optimizer='adam',\n",
    "    # 损失函数\n",
    "    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits= False),\n",
    "    # 评测方法\n",
    "    metrics=['sparse_categorical_accuracy']\n",
    ")\n",
    "# 5，训练\n",
    "model.fit(\n",
    "    x_train, y_train, batch_size=32, epochs=5, \n",
    "    validation_data=(x_test, y_test), validation_freq=1)\n",
    "model.summary()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e6044c99-0741-4378-b2b6-f60c293cc3a9",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.10"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
